Skip to content

Subsystem 3d: team presence events (connect/disconnect/death/respawn/AFK) + in-game !afk - #17

Merged
HandyS11 merged 14 commits into
developfrom
feat/team-presence-events
Jun 18, 2026
Merged

Subsystem 3d: team presence events (connect/disconnect/death/respawn/AFK) + in-game !afk#17
HandyS11 merged 14 commits into
developfrom
feat/team-presence-events

Conversation

@HandyS11

Copy link
Copy Markdown
Owner

Subsystem 3d — Team presence events

Fires Discord #events embeds and in-game team-chat lines whenever a team member connects / disconnects / dies / respawns, plus AFK (became-AFK / returned-from-AFK), and adds the in-game !afk command. Always-on while a server is connected — no toggle.

Scope is team members only (the Rust+ companion API only exposes presence/life state for the paired account's own in-game team). Builds on subsystems 2a (live-events relay pattern) and 3b-ii (team-intel via GetTeamInfoAsync).

How it works

  • The ConnectionSupervisor poll loop diffs successive GetTeamInfoAsync snapshots via a new pure, unit-tested TeamStateTracker, publishing PlayerStateChangedEvent on the in-process bus. First snapshot (and brand-new members) prime silently; a null/failed poll keeps the baseline and emits nothing.
  • A new RustPlusBot.Features.Players slice (mirroring Features.Events) consumes the event: PlayersHostedServicePlayerEventRelay#events embed (via IEventChannelLocator) + in-game line (via ITeamChatSender), EN/FR localized.
  • Death-location resolution: leader → TeamInfo.DeathNote (the true death spot); non-leader → the previous-poll position (captured pre-death, so a slow poll never mislabels the respawn point); neither → no location. Location shows only on death/respawn/became-AFK.
  • AFK: still (within AfkEpsilon) and online and alive for ≥ AfkThreshold (config, default 5m), hysteresis-latched so became-AFK fires once; moving / going offline / dying clears it. Exposed via a new IAfkState read seam over the supervisor's live tracker, surfaced as in-game !afk (no /players collision — that name stays reserved for the future Battlemetrics cluster).

Tests

404 across 10 assemblies, all green. New: TeamStateTrackerTests (12), TeamStateTrackerAfkTests (5), AfkStateTests (2), the full Features.Players.Tests suite (12), AfkCommandHandlerTests, registration + help-drift updates. dotnet build -warnaserror clean.

Notes

  • TeamStateTracker is guarded by a lock (mirroring EventStateStore) because the !afk command thread reads it concurrently with the poll loop.
  • Lays the position-tracking groundwork the catalog flagged for the deferred !connections / !deaths history and was the prerequisite for !afk.

🤖 Generated with Claude Code

HandyS11 and others added 13 commits June 18, 2026 20:51
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add afk to CommandHelpCatalog.InGame (CommandGroup.TeamIntel, help.afk),
add help.afk EN/FR descriptions to CommandLocalizationCatalog, and update
CommandHelpCatalogTests.HandlerNames from 12 to 13 entries to keep the
bidirectional drift test green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…afk reads

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 18, 2026 19:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new “Players” feature slice that detects and relays team presence/life-state transitions (connect/disconnect/death/respawn + AFK enter/exit) by diffing successive Rust+ GetTeamInfoAsync snapshots and publishing a new in-process PlayerStateChangedEvent, which is then relayed to Discord #events and in-game team chat. It also introduces a new in-game !afk command backed by a live AFK state seam exposed from the connection supervisor.

Changes:

  • Add TeamStateTracker to diff team snapshots into PlayerTransitions (including AFK hysteresis) and publish PlayerStateChangedEvent from the supervisor poll loop.
  • Add RustPlusBot.Features.Players to render/relay those transitions to Discord embeds + in-game lines with EN/FR localization.
  • Add IAfkState + !afk command and update command/help catalogs and registrations accordingly.

Reviewed changes

Copilot reviewed 40 out of 40 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/RustPlusBot.Features.Players.Tests/RustPlusBot.Features.Players.Tests.csproj Adds new test project for the Players feature.
tests/RustPlusBot.Features.Players.Tests/PlayerLocalizationCatalogTests.cs Validates player-event localization catalog + fallback formatting.
tests/RustPlusBot.Features.Players.Tests/PlayerEventRendererTests.cs Tests in-game line rendering for key transition types.
tests/RustPlusBot.Features.Players.Tests/PlayerEventRelayTests.cs Tests relay behavior for in-game send + optional Discord posting.
tests/RustPlusBot.Features.Players.Tests/PlayerEventRegistrationTests.cs Verifies Players DI wiring resolves.
tests/RustPlusBot.Features.Players.Tests/PlayerEventEndToEndTests.cs Ensures all transition kinds render without throwing.
tests/RustPlusBot.Features.Connections.Tests/TeamStateTrackerTests.cs Adds unit coverage for connect/disconnect/death/respawn diffing and death-location rules.
tests/RustPlusBot.Features.Connections.Tests/TeamStateTrackerAfkTests.cs Adds unit coverage for AFK threshold/hysteresis behavior.
tests/RustPlusBot.Features.Connections.Tests/TeamInfoSnapshotTests.cs Covers new DeathNote field behavior.
tests/RustPlusBot.Features.Connections.Tests/AfkStateTests.cs Tests CurrentAfk reporting surface.
tests/RustPlusBot.Features.Commands.Tests/Help/CommandHelpCatalogTests.cs Updates handler-name inventory for the new afk handler.
tests/RustPlusBot.Features.Commands.Tests/CommandRegistrationTests.cs Updates command DI tests for IAfkState + handler count and presence.
tests/RustPlusBot.Features.Commands.Tests/AfkCommandHandlerTests.cs Adds tests for new !afk command behavior.
src/RustPlusBot.Host/RustPlusBot.Host.csproj Adds Players feature project reference to host.
src/RustPlusBot.Host/Program.cs Registers Players feature via AddPlayers().
src/RustPlusBot.Features.Players/RustPlusBot.Features.Players.csproj New Players feature project definition + references.
src/RustPlusBot.Features.Players/Rendering/PlayerLocalizer.cs Adds player-event localizer with fallback + culture normalization.
src/RustPlusBot.Features.Players/Rendering/PlayerLocalizationCatalog.cs Adds EN/FR player-event string catalog.
src/RustPlusBot.Features.Players/Rendering/PlayerEventRenderer.cs Renders transitions as Discord embeds and in-game lines (grid refs).
src/RustPlusBot.Features.Players/Rendering/IPlayerLocalizer.cs Localizer abstraction for player-event rendering.
src/RustPlusBot.Features.Players/Relaying/PlayerEventRelay.cs Relays transitions to in-game chat and optionally to Discord channel.
src/RustPlusBot.Features.Players/Posting/IPlayerChannelPoster.cs Abstraction for Discord posting.
src/RustPlusBot.Features.Players/Posting/DiscordPlayerChannelPoster.cs Discord.Net implementation for posting embeds.
src/RustPlusBot.Features.Players/PlayerEventServiceCollectionExtensions.cs DI registration for Players feature slice.
src/RustPlusBot.Features.Players/Hosting/PlayersHostedService.cs Event bus consumer that drives relay loop.
src/RustPlusBot.Features.Connections/Supervisor/ConnectionSupervisor.cs Adds team-info polling + tracker + event publication; exposes IAfkState.
src/RustPlusBot.Features.Connections/Listening/TeamStateTracker.cs New tracker implementing snapshot diff + AFK hysteresis + AFK query surface.
src/RustPlusBot.Features.Connections/Listening/RustPlusSocketSource.cs Maps Rust+ DeathNote into TeamInfoSnapshot.
src/RustPlusBot.Features.Connections/Listening/IAfkState.cs New seam exposing live AFK members for !afk.
src/RustPlusBot.Features.Connections/ConnectionServiceCollectionExtensions.cs Registers IAfkState from ConnectionSupervisor.
src/RustPlusBot.Features.Connections/ConnectionOptions.cs Adds AfkThreshold and AfkEpsilon options.
src/RustPlusBot.Features.Commands/Localization/CommandLocalizationCatalog.cs Adds EN/FR strings for !afk responses + help.
src/RustPlusBot.Features.Commands/Help/CommandHelpCatalog.cs Adds afk to help listing.
src/RustPlusBot.Features.Commands/Handlers/AfkCommandHandler.cs Implements !afk command.
src/RustPlusBot.Features.Commands/CommandServiceCollectionExtensions.cs Registers AfkCommandHandler.
src/RustPlusBot.Abstractions/Events/PlayerTransitionKind.cs Introduces transition kind enum for player presence events.
src/RustPlusBot.Abstractions/Events/PlayerTransition.cs Introduces transition payload record.
src/RustPlusBot.Abstractions/Events/PlayerStateChangedEvent.cs Introduces published event carrying transitions + map dimensions.
src/RustPlusBot.Abstractions/Connections/TeamInfoSnapshot.cs Extends snapshot with optional leader DeathNote coordinate.
RustPlusBot.slnx Adds Players feature project + test project to solution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +89 to +99
var eligible = now.IsOnline && now.IsAlive;
if (!eligible)
{
if (_afk.Remove(id))
{
transitions.Add(new PlayerTransition(PlayerTransitionKind.ReturnedFromAfk, id, now.Name, null));
}

_stillSince[id] = clock;
return;
}
return;
}

var moved = Math.Abs(now.X - was.X) > epsilon || Math.Abs(now.Y - was.Y) > epsilon;
Comment on lines +55 to +56
_baseline = current;
return transitions;
- AFK: clear the latch SILENTLY when a member goes offline, is dead, or
  died this poll (detected via LastDeathTimeUtc advancing even on a slow
  poll where IsAlive already flipped back), instead of emitting a
  contradictory ReturnedFromAfk alongside the disconnect/death line.
- AFK movement: use a squared-distance check so AfkEpsilon is a true
  movement radius (per-axis treated diagonal moves as still).
- Prune per-member stillness/AFK state for ids absent from the snapshot
  (members who leave the team) so the maps don't grow unbounded.
- Add player.title to the localization-catalog key-coverage test.
- Run jb cleanupcode (ReformatAndReorder) — fixes the Linux CI format gate
  that the original push skipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HandyS11

Copy link
Copy Markdown
Owner Author

Addressed the Copilot review + fixed the Linux CI failure in 7e41a82:

  • AFK contradictory output (TeamStateTracker.cs:99): going offline / dying / dying-this-poll (incl. the slow-poll case where LastDeathTimeUtc advances but IsAlive already shows respawned) now clears the AFK latch silently — no more "Bob disconnected" + "Bob is back" in the same poll. ReturnedFromAfk is emitted only when a member moves out of AFK while still online+alive.
  • Diagonal movement (TeamStateTracker.cs:101): switched to a squared-distance check so AfkEpsilon is a true movement radius (dx=dy=0.8, eps=1 → dist≈1.13 now correctly counts as moved).
  • Unbounded state (TeamStateTracker.cs:56): added PruneDepartedMembers — per-member stillness/AFK state is dropped for ids absent from the current snapshot (members who leave the team) before the baseline is replaced.
  • Catalog test (PlayerLocalizationCatalogTests.cs:10): added player.title to the validated key list.

New AFK tests cover the silent-clear-on-offline, silent-clear-on-death-while-respawned, diagonal-move, and departed-member-pruning cases. Full suite 407 green across 10 assemblies; build clean under -warnaserror. The Linux failure was the jb cleanupcode (ReSharper) format gate — the original push skipped it; now run and committed, CI green on both OSes.

@HandyS11
HandyS11 merged commit 8d8b656 into develop Jun 18, 2026
3 checks passed
@HandyS11
HandyS11 deleted the feat/team-presence-events branch June 18, 2026 20:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants